<template>
    <div
        class="dargtabel"
        :style="{
            height: `${Math.ceil(datas?.length / configs.colNum) * configs.height}px`,
            width: `${configs.colNum * configs.width}px`,
        }"
    >
        <div
            v-for="item in text"
            :key="item.name"
            class="dargtabel_cell"
            :style="{
                width: item.width + 'px',
                height: item.height + 'px',
                position: 'absolute',
                top: item.top + 'px',
                left: item.left + 'px',
                zIndex: item.zIndex,
                transition: item.transition,
            }"
            @mousedown="onDrag($event, item)"
            @mousemove="onDragstart($event, item)"
            @mouseup="onMouseup(item)"
            @mouseleave="onMouseup(item)"
            @touchstart="onDrag($event, item)"
            @touchend.prevent.stop="onMouseup(item)"
            @touchmove.prevent.stop="onDragstart($event, item)"
        >
            <div class="dargtabel_cell-items">
                <!-- 内容使用具名插槽 使用方法类似于teble -->
                <div class="dargtabel_cell-items-content">
                    <slot name="content" :item="item.data">
                        <div class="dargtabel_cell-items-content-title">标题</div>
                        <div class="dargtabel_cell-items-content-text">
                            <p>{{ item.data.color }}</p>
                        </div>
                    </slot>
                </div>
            </div>
        </div>
    </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, reactive, ref } from 'vue'

interface ConfigTypes {
  width: String | number;
  height: String | number;
  cx: number;
  cy: number;
  colNum: number;
  datas: any;
}
/**
 * 拖拽排序
 * 移动端体验更佳+支持多排换位置
 * 有其他需求可以拓展数据
 */
const props = {
    //  数据类型 作为唯一标识id必须存在
    datas: {
        type: Array,
    },
    //  节点宽度
    boxWidth: {
        type: [String, Number],
    },
    //   节点高度
    boxHeight: {
        type: [String, Number],
    },
    //   列数
    colNum: {
        type: Number,
    },
}
export default defineComponent({
    name: 'Drags',
    props,
    setup(props) {
        let configs = reactive<ConfigTypes>({
            width: props.boxWidth ?? 231, // 宽
            height: props.boxHeight ?? 200, // 高
            cx: 0, // 移动虚拟位置
            cy: 0, // 虚拟位置
            colNum: props.colNum ?? 5, // 排数
            datas: props.datas,
        })
        const isMove = ref()
        isMove.value = false
        const text = ref() // 展示内容数组
        let col = 0 // 列
        let row = -1 // 行
        let count = 0 // 计数器
        // 初始化函数 过滤数据与设置排数
        onMounted(() => {
            initTable()
        })
        // 初始化函数
        const initTable = () => {
            text.value = configs.datas.map((item) => {
                row++
                count++
                let objs = {
                    id: item.id,
                    data: item,
                    top: col * parseInt(configs.height as string),
                    left: parseInt(configs.width as string) * row, // 拖动位置
                    width: configs.width,
                    height: configs.height,
                    ileft: parseInt(configs.width as string) * row, // 真实位置
                    itop: col * parseInt(configs.height as string),
                    zIndex: 1,
                    transition: 'none',
                }
                // 达到列限制就会自动换行
                if (count > configs.colNum - 1) {
                    col++
                    row = -1
                    count = 0
                }

                return objs
            })
        }
        // 点击按下
        const onDrag = (e, el) => {
            let lefts = e.clientX ?? e.targetTouches[0].pageX
            let tops = e.clientY ?? e.targetTouches[0].pageY
            configs.cx = lefts - el.left
            configs.cy = tops - el.top
            isMove.value = true
            el.zIndex = 999
        }
        // 移动开始
        const onDragstart = (e, el) => {
            e.preventDefault()
            if (isMove.value) {
                let lefts = e.clientX ?? e.targetTouches[0].pageX
                let tops = e.clientY ?? e.targetTouches[0].pageY
                el.top = tops - configs.cy
                el.left = lefts - configs.cx
                let time = el.top + el.left // 过度事件根据移动的距离设置
                time = time < 300 ? 300 : time
                addTransition(el, time)
                toMove(el)
            }
            return false
        }
        // 鼠标抬起的时候如果没有替换位置需要 返回原来的位置
        const onMouseup = (el) => {
            removeTransition()
            el.top = el.itop
            el.left = el.ileft
            el.zIndex = 1
            isMove.value = false
        }
        // 检测并替换位置
        const toMove = (el) => {
            let { left: l, top: t, id: m } = el
            text.value.forEach((item) => {
                // 移动的滑块移动的top与left值与目标值的差必须小于40才能进行排序 差值越小越好 不然会出现错误
                if (
                    Math.abs(l - item.left) < el.width / 4
          && Math.abs(t - item.top) < el.height / 4
          && item.id != m
                ) {
                    replce(item, el)
                }
            })
        }
        // 位置替换
        const replce = (node1, node2) => {
            let { itop: top1, ileft: left1 } = node1
            let { itop: top2, ileft: left2 } = node2
            Object.assign(node1 ?? {}, {
                top: top2,
                left: left2,
                ileft: left2,
                itop: top2,
            })
            Object.assign(node2 ?? {}, {
                top: top1,
                left: left1,
                ileft: left1,
                itop: top1,
            })
        }
        // 添加过度效果
        const addTransition = (el, time) => {
            text.value.forEach((item) => {
                if (el.id !== item.id) {
                    item.transition = `all ${time}ms ease 0s`
                }
            })
        }
        // 移除过度效果
        const removeTransition = () => {
            text.value.forEach((item) => {
                item.transition = 'none'
            })
        }
        return {
            text,
            configs,
            onDrag,
            onDragstart,
            onMouseup,
        }
    },
})
</script>

<style lang="less" scoped>
.dargtabel {
  position: relative;
  margin: 0 auto;
  &_cell {
    cursor: pointer;
    display: flex;
    align-items: center;
    &-items {
      width: 95%;
      height: 95%;
      background: #fff;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
      border-radius: 5px;
      overflow: hidden;
      &-content {
        display: flex;
        flex-direction: column;
        &-title {
          padding: 5px 3px;
          height: 30px;
          background: burlywood;
          font-size: 12px;
          color: #666;
        }
        &-text {
          font-size: 11px;
          padding: 3px 3px;
          text-align: center;
          align-items: center;
          height: 160px;
          line-height: 160px;
          //   background: red;
          p {
            display: inline-block;
            margin: 0;
          }
        }
      }
    }
  }
}
</style>
